Repository navigation
feat(flags-core): add header-driven Vercel mode on the shared cache - #511
Merged
luismeyer merged 47 commits intoOct 9, 2026
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
luismeyer
added this pull request to stack #512
September 23, 2026 08:05
luismeyer
commented
Sep 23, 2026
luismeyer
commented
Sep 23, 2026
luismeyer
commented
Sep 23, 2026
luismeyer
commented
Sep 23, 2026
luismeyer
commented
Sep 23, 2026
luismeyer
commented
Sep 23, 2026
luismeyer
commented
Sep 23, 2026
luismeyer
commented
Sep 23, 2026
…ce response as recovery Polling now starts only once the stream has given up for good (retries exhausted, 401, or token failure); transient disconnects and startup timeouts keep serving the cache while the stream reconnects. Stream and polling freshness extend their fresh window by staleWhileRevalidate before reads block. getDatafile() is a snapshot again and never starts a source. Any successful source response renews cache age and clears the failure deadline even when the version guard keeps the stored data, a last-resort fetch clears failure too, and clear() resets the deadline so reinitialization after shutdown starts clean and rewires source events. A 401 on an empty cache no longer disables the client permanently. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjWjQyvVvunuerU51GvB9M
…out polling With a recorded failure the cache otherwise waits for the live source to recover, but a stream-only client has no source left once the stream gives up. Reads in that state now revalidate over HTTP, including after a failed refresh. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjWjQyvVvunuerU51GvB9M
…t getDatafile Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjWjQyvVvunuerU51GvB9M
…s while degraded The controller no longer tracks a separate streamExhausted flag: stream startup reports connected/connecting/failed and the state plus StreamSource.active carry the rest. Degraded now means no live source is active, and degraded reads apply stale-while-revalidate over HTTP on the configured source's schedule, treating unknown age as stale so data recovers once the stream stops for good. Polling gives up on a 401 and enters degraded, and a retried initialization after a failed first poll keeps the active polling source instead of leaving it in degraded. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…staleIfError only Degraded reads no longer borrow the stream or polling schedules. Data refreshed within staleWhileRevalidate is served as is, older data is served while a background refresh runs, and data older than staleWhileRevalidate plus staleIfError waits for the refresh. Unknown age refreshes in the background. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…after suspension While streaming, stale reads serve the cache and expired reads wait for the stream to confirm instead of starting an HTTP fetch that the next ping would cancel. StreamSource.revalidate() drops a connection silent beyond the fresh window (keeping a young replacement), and waits up to the fetch deadline for the next message; CacheReadPolicy.revalidate routes stale and expired reads through it. Polling, header mode and degraded reads keep refreshing over HTTP. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…equest Module-scope initialization still starts no network activity in Vercel mode. Inside a request, initialize() now prepares the cache the way the first evaluation would: a matching version header confirms it, a newer version or an empty cache fetches, and a missing entry starts streaming or polling. It rejects only when no definitions are available. Runtime setup happens once per lifecycle, so repeated calls in later requests only repeat the preparation. Lazy initialization from reads passes prepareRequest: false, because the read resolves the request itself and must keep accurate cache metrics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Streaming data now stays stale until five minutes after the last message, or until the fresh window plus staleWhileRevalidate if that is longer. A runtime resuming from an ordinary suspension serves the cache while the ping watchdog reconnects in the background, instead of blocking the read on a stream reconnect. Reads only wait for the stream once the connection has been silent beyond that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Streaming reads now always serve the cache, reporting data older than one missed ping as stale, and never start HTTP work while the stream is live. Recovery stays with the stream: the ping watchdog reconnects silent connections, including right after a suspended runtime resumes, and exhausted retries fall back to polling or HTTP revalidation. Production metrics showed rare ~2s (up to 10s) warm precompute() waits from the blocking path. This removes the stream revalidation wait (StreamSource.revalidate, the per-connection reconnect handle and the cache's resolveThroughSource) and the five-minute streaming expiry, which only existed to bound that wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #510.
Adds header-driven reads on Vercel, enabled by default when
VERCEL=1. Initialization loads provided or bundled definitions. Evaluations andgetDatafile()share project-specific header checks, stale-while-revalidate, blocking refresh, stale-if-error, and source fallback. Provider discovery andexperimental_reportOverridecontinue using the publicgetDatafile()method.Header mode requires a valid positive version for the client's own project and a valid cached config version. Missing, malformed, or unrelated entries switch that client to streaming when enabled, otherwise polling. Clients select sources independently within a request. A cold read first discovers project identity through the shared cache fetch; a failed cold fetch rejects without switching sources and can be retried. Subsequent source assessments return an object containing data, cache status, and a source-error indicator. Assessment errors trigger fallback without renewing the fetch-failure deadline, including when stale-if-error has expired.
The cache owns freshness, serving decisions, deduplicated refreshes, cancellation, and failure deadlines. Accepted stream updates and valid confirmations cancel superseded HTTP work; waiting reads use confirmed data, and late responses cannot change cache or authorization state.
initializePolling(). Reads share the immediate poll and wait for its completion or the configured initialization timeout, even with cached data. Timeout fallback respects stale-if-error while polling continues. Detached disconnect startup handles rejection, while waiting reads still receive errors.fetchedAt. Initialization timeouts and repeated errors do not renew failure allowances.staleWhileRevalidatedefaults to ten seconds andstaleIfErrorto Infinity. Public durations accept fractional seconds; zero disables the corresponding stale allowance. Build/offline loading retains its existing policy.Validation: all 1,012 core unit tests pass on Node 22 and Node 24. TypeScript, changed-file Biome, and diff checks pass. Coverage includes multi-project clients, unified reads and refresh sharing, failed cold-fetch retries, polling completion and timeout fallback, quiet ping recovery, stream reconnection, and shutdown with and without waiting reads. Live-service integration tests were excluded.